package com.atg.openssp.core.exchange;

import com.atg.openssp.common.core.entry.BiddingServiceInfo;
import com.atg.openssp.common.core.exchange.Auction;
import com.atg.openssp.common.core.exchange.Exchange;
import com.atg.openssp.common.core.exchange.ExchangeExecutorServiceFacade;
import com.atg.openssp.common.core.exchange.RequestSessionAgent;
import com.atg.openssp.common.exception.RequestException;
import com.atg.openssp.common.provider.AdProviderReader;
import openrtb.bidrequest.model.Site;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import util.math.FloatComparator;

import java.io.IOException;
import java.io.Writer;
import java.util.List;
import java.util.Map;
import java.util.concurrent.Callable;
import java.util.concurrent.CancellationException;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;

/**
 * This is the server which is mainly responsible to start the bidprocess, collect the result and build a reponse for the client.
 * 通过ExchangeServer 可以对接不同的channel 其他的dsp adx 其他的ssp
 * @author André Schmer
 *
 */
public class ExchangeServer implements Exchange<RequestSessionAgent> {

	private static final Logger LOG = LoggerFactory.getLogger(ExchangeServer.class);
    public static final String SCHEME = "http";

	/**
	 * Starts the process to exchange and build a response if a {@code VideoResult} can be expected.
	 * <p>
	 * Principle of work is the following:
	 * <ul>
	 * <li>fetch a list of channels</li>
	 * <li>invoke the callables due to the {@link ExchangeExecutorServiceFacade}</li>
	 * <li>Evaluates a winner from the list of futures by making a simple price comparison where the highest price wins</li>
	 * <li>If a valid winner is evaluated, the response is build and post bid operations such as winningnotifying can be processed</li>
	 * </ul>
	 *
	 * @param {@link
	 *            RequestSessionAgent}
	 * 
	 * @return true if a provider, {@link AdProviderReader}, exists and building a response is successful, false otherwise
	 *
	 * 选择一个最高价作为一个竞价赢家
	 */
	@Override
	public boolean processExchange(final RequestSessionAgent agent) throws ExecutionException, RequestException {
		//向dsp-sim请求获取一个赢家
		final AdProviderReader winner = execute(agent);
		//计算竞价返回结果
		return evaluateResponse(agent, winner);
	}

	/**
	 * @param agent
	 * @return
	 * @throws ExecutionException
	 * @throws RequestException
	 * 通过计算获取一个广告位竞价赢家
	 * 如果是订单合约模式则一定有返回
	 */
	private AdProviderReader execute(final RequestSessionAgent agent) throws ExecutionException, RequestException {
		try {
			/**
			 * 在createListOfChannels中拼装bidrequest,因为ssp系统可以对接其他的adx或者是直接对接dsp所以此处会存在多个
			 * 请求渠道
			 */
			final List<Callable<AdProviderReader>> callables = ChannelFactory.createListOfChannels(agent);
			//提交callables集合中的任务包括竞价请求
			final List<Future<AdProviderReader>> futures = ExchangeExecutorServiceFacade.instance.invokeAll(callables);
			//规约的计算出一个竞价赢家
			final Future<AdProviderReader> winnerFuture = futures.stream().reduce(ExchangeServer::validate).orElse(null);
			if (winnerFuture != null) {
				try {
					return winnerFuture.get();
				} catch (ArrayIndexOutOfBoundsException ex) {
					LOG.error("no winner detected (winnerFuture is empty)");
				} catch (final ExecutionException e) {
					if (e.getCause() instanceof RequestException) {
						throw (RequestException) e.getCause();
					} else {
                        LOG.error(e.getMessage(), e);
					}
					throw e;
				}
			} else {
                LOG.error("no winner detected");
			}
		} catch (final InterruptedException e) {
            LOG.error(e.getMessage());
		}
		return null;
	}

	private static Future<AdProviderReader> validate(final Future<AdProviderReader> a, final Future<AdProviderReader> b) {
		try {
			if (b.get() == null) {
				return a;
			}
			if (a.get() == null) {
				return b;
			}

			if (FloatComparator.greaterThanWithPrecision(a.get().getExchangedCurrencyPrice(), b.get().getExchangedCurrencyPrice())) {
				return a;
			}
		} catch (final InterruptedException e) {
            LOG.error(e.getMessage());
		} catch (final CancellationException e) {
            LOG.error(e.getMessage());
		} catch (final ExecutionException e) {
            LOG.error(e.getMessage(), e);
		}

		return b;
	}

	private boolean evaluateResponse(final RequestSessionAgent agent, final AdProviderReader winner) {
        LOG.debug("evaluateResponse");
		BiddingServiceInfo info = agent.getBiddingServiceInfo();

		agent.getHttpResponse().setCharacterEncoding(info.getCharacterEncoding());
		agent.getHttpResponse().setContentType("Content-Type: "+info.getContentType());

		if (info.isAccessAllowOriginActivated() && winner instanceof Auction.AuctionResult) {
			//头部竞价结果处理
			/**
			 * 头部竞价指发布商可以将first look的竞价机会优先发送给多个程序化交易合作伙伴（即买家）。
			 * 这些合作伙伴只需要在发布商媒体页面头部嵌入一段Java代码，就可以接收到发布商优先发送的广告请求，并完成竞价
			 */
            LOG.debug("is HeaderBid AuctionResult");
			if (((Auction.AuctionResult)winner).getBidRequest() != null) {
				//TODO:  BKS need app
				agent.getHttpResponse().addHeader("Access-Control-Allow-Origin", SCHEME+"://" + ((Auction.AuctionResult) winner).getBidRequest().getSite().getDomain());
				agent.getHttpResponse().addHeader("Access-Control-Allow-Methods", "POST");
				agent.getHttpResponse().addHeader("Access-Control-Allow-Headers", "Content-Type");
				agent.getHttpResponse().addHeader("Access-Control-Allow-Credentials", "true");
			} else {
                agent.getHttpResponse().addHeader("Access-Control-Allow-Origin", SCHEME+"://" + info.getSite().getDomain());
                agent.getHttpResponse().addHeader("Access-Control-Allow-Methods", "POST");
                agent.getHttpResponse().addHeader("Access-Control-Allow-Headers", "Content-Type");
                agent.getHttpResponse().addHeader("Access-Control-Allow-Credentials", "true");
            }
		} else {
		    Site site = agent.getBiddingServiceInfo().getSite();
            agent.getHttpResponse().addHeader("Access-Control-Allow-Origin", SCHEME+"://" + site.getDomain());
            agent.getHttpResponse().addHeader("Access-Control-Allow-Methods", "POST");
            agent.getHttpResponse().addHeader("Access-Control-Allow-Headers", "Content-Type");
            agent.getHttpResponse().addHeader("Access-Control-Allow-Credentials", "true");
        }
		Map<String, String> headers = info.getHeaders();
		for (Map.Entry<String, String> entry : headers.entrySet()) {
			agent.getHttpResponse().addHeader(entry.getKey(), entry.getValue());
		}
		//遵循RTB标准响应请求结果
		try (Writer out = agent.getHttpResponse().getWriter()) {
			if (winner != null && winner.isValid()) {

				final String responseData;
				if (winner instanceof Auction.AuctionResult) {
					if (((Auction.AuctionResult)winner).getBidRequest() != null) {
						responseData = ((Auction.AuctionResult) winner).buildHeaderBidResponse();
					} else {
						responseData = "";
					}
				} else {
					//针对多种竞价返回结果处理响应结果
					responseData = winner.buildResponse();
				}
				out.append(responseData);

                if (agent.getBiddingServiceInfo().sendNurlNotifications()) {
                    LOG.debug("send winning nurl notification");
                    //异步发送赢家通知dsp系统进行统计
                    winner.perform(agent);
                } else {
                    LOG.debug("skipping winning nurl notification on header bidding");
                }
				out.flush();
				return true;
			}
		} catch (final IOException e) {
			LOG.error(e.getMessage(), e);
		}
		return false;
	}

}
